上一篇文章大家如果仔细阅读揣摩对RN有了一个初步的认识了,接下来将基于上一篇文章的这种初步认识然我们详细了解一下RN的启动过程
启动过程
[RCTRootView initWithBundleURL:…]
[RCTBridge initWithBundleURL:…]
[RCTBridge setUp]
初始化batchedBridge
[RCTCxxBridge start]
开启一个线程jsThread用于js
[RCTCxxBridge _initModulesWithDispatchGroup]
初始化JSCExecutorFactory, 用于执行js代码以及处理回调 JSCExecutor::JSCExecutor()
JSCExecutor::initOnJSVMThread()
installGlobalProxy -> nativeModuleProxy
RCTJavaScriptLoader -> 加载js代码
[RCTCxxBridge executeSourceCode]
[RCTRootView initWithBridge:…]
[RCTRootView bundleFinishedLoading]
初始化RCTRootContentView
[RCTRootView runApplication]//是Native调用js的一个完美例子。本质调用了AppRegistry的runApplication方法
我们初始化RN工程一般都是如下代码:
1、 获取到app的js入口文件的NSURL
2 、初始化RCTRootView, 传递的参数moduleName:@”AwesomeProject”是我们在入口的js文件中注册的, initialProperties:nil, 将作为js中的跟view的props, 可以用来传递需要的初始数据
进入RCTRootView初始化中,其实很简单
就实例化了一个RCTBridge,这个RCTBridge的实例其实就是一个外壳而已,接下来我们看看内部,为啥是一个外壳。
内部中这个方法就是关键,这个方法里边实例化了一个真正的Bridge,即RCTCxxBridge
或RCTBatchedBridge
,所以说一开始实例化的Bridge只是一个壳子而已。为什么会出现两个不同的Bridge呢?主要是0.44之前的都是用的RCTBatchedBridge
,之后RN用的是RCTCxxBridge
,根据它的官方资料RCTCxxBridge
会慢慢取代RCTBatchedBridge
。(RCTCxxBridge
有个问题就是依赖了4个包,而且这4个包被墙了具体可以看将RN工程嵌入到现有原生iOS应用)。
首先是bridgeClass的获取,具体代码是优先加载RCTCxxBridge
,如果获取不到的话,其次加载RCTBatchedBridge
。本次分析是基于0.48并且工程初始化是使用了RCTCxxBridge
,所以下边只要提到Bridge说的就是RCTCxxBridge
。
start是一个非常有料的方法。就像之前说的Bridge其实就是实现了一个调度管理的作用,那调度管理什么呢?所有需要调度管理的前提环境和类全部都是在start方法进行了初始化。
1、 首先做的是实例化了一个js线程,我们之前所说的js thread就是它,用来执行js代码的线程
这个线程执行的方法是runJSRunLoop
通过名字大家也能猜出一个大概吧。其实就是启动了线程的runloop,保证了_jsThread能够一直运行。
2、创建了一个group,用来初始化Birdge。接下来就开始加载我们本地所有的已经实现了RCTBridgeModule协议的modules
3、让我们看看initModulesWithDispatchGroup内部
主要是循环遍历用2个数组分别存储了所有的class和RCTModuleData(存储着我们class所有的信息包括class名称、所有的方法、等一些有效的信息和一个instance。还有一个Dictionary key是class,value是moudleData。这个就是通过class名称去取moduleData用的。
这个方法中有一个很重要的方法RCTGetModuleClasses()
。
这个就是我们一直说的必须要实现了RCTBridgeModule这个协议,如果实现其实会直接报错的。这个加载是通过RCT_EXPORT_MODULE()
宏来实现的,宏是通过运行时+load()
方法在我们应用启动的时候其实已经将所有的module加入到了数组之中。
4 、以上终于完成了Modules信息的保存, 接着会进行JSExecutorFactory的初始化和相关的设置, JSExecutorFactory内部会持有一个JSCExecutor 所有与JS的通信,一定都通过JSCExecutor来进行, 所以需要重点关注, 它的构造函数中调用了initOnJSVMThread(), 里面进行了很多的JS上下文的准备, 创建JSClass, 全局的context, 以及添加全局的回调.注意在这个里面使用到的JSCJSXXX的宏的作用, 实际上是会转换为调用苹果的JavaScriptCore对应的方法(去掉JSC), 同时还要留意JSCExecutor构造函数中为js的上下文中设置了一个Proxy, nativeModuleProxy.
为js的上下设置 nativeModuleProxy, 这一个属性很重要, js端用来获取所有注册的nativeModule. 在js中我们需要引入native端的时候回写这样的类似代码 var {NativeModules} from ‘react-native’, 实际上就是获取到我们这里设置的.
那么js中是怎么获取到我们之前在native端已经生成好的’配置表’信息的呢? 这个问题需要我们重点关注下, 在之前的react-native版本中, 是通过native端直接在js的上下文中注入这样一个__fbBatchedBridgeConfig配置表,
传过去一个json(remoteModuleConfig). 现在的版本中改了一些. 还是上面这段代码. 在如下的调用栈中,我们只需要关注最后一个函数.
JSCExecutor::getNativeModule()
JSCNativeModules::getModule()
JSCNativeModules::createModule()
ModuleRegistry::getConfig()
RCTNativeModule::getMethods()
NSStringFromSelector(selector) hasPrefix:@”rct_export”(初始化RCTModuleMethod信息)
5、 使用dispatch_group方式初始化Instance(执行代码需要), 和加载js代码RCTJavaScriptLoader,这个类比较单纯,就是用来加载js代码的,没有其他用处
6、 modules and source code都已经准备好了, 在notify中JSCExecutor专属的Thread内执行jsbundle代码执行js代码. 执行完毕后会将_displayLink添加到runloop(注意是在jsThread所在的runloop)中, 开始运行。
[RCTCxxBridge executeSourceCode: ]
[RCTCxxBridge enqueueApplicationScript:]
void Instance::loadScriptFromString()
void NativeToJsBridge::loadApplication()
void JSCExecutor::loadApplicationScript()
void JSCExecutor::flush()
void JSCExecutor::bindBridge()
上面的调用栈中我们主要关注后面几个函数
void JSCExecutor::loadApplicationScript()
, 在这个函数中注意下面两行代码, 注意, RN中有个很明显的问题就是, 首次进入RN模块的时候, 加载的很慢, 会有几秒的加载时间, 其实就是在这个函数中加载jsBundle造成的, 如果要优化加载的时间, 以及不同模块页面切换流畅, 就需要对jsBundle文件进行精简, 缓存等操作。
void JSCExecutor::bindBridge()保存js的上下文环境中必要的全局属性和回调, 这些在 react native的 MessageQueue.js中定义的调用方法, 当native需要调用js的方法的时候, 需要通过调用这些js方法, 在这些方法中, js端会根据传递的信息查找到在js中需要调用的方法.
7 、上面的工作完成之后, 初始化bridge的工作就完成了, jsBundle已经加载完成, oc端的’配置表’也已经处理好, 并且成功已经传递给js端, js的上下文配置已经准备好, 下面就是开始执行我们的js代码了, React就会开始计算好所有的布局信息, 以及Component层级关系等, 等待native端完成对应的真正的页面渲染和布局. 回到RCTRootView的初始化方法中, 注意在[RCTRootView initWithBridge:…]的初始化方法中, 注册了几个js执行情况的通知, 我们重点关注js执行完毕后的通知RCTJavaScriptDidLoadNotification
下面是js执行完毕后的通知RCTJavaScriptDidLoadNotification中的一系列的处理
[RCTRootView javaScriptDidLoad]
[RCTRootView bundleFinishedLoading]
RCTRootContentView (创建contentView)
[RCTRootView runApplication]
在创建RCTRootContentView的时候, 注意有个参数是reactTag, 这个属性很重要, 每一个reactTag都应该是唯一的, 从1开始, 每次递增10. RCTRootContentView初始化时, 还需要在RCTUIManager中通过reactTag去注册, 从而由RCTUIManager来统一管理所有的js端使用Component对应的每个原生view(_viewRegistry[tag]表), 有了这个, 我们就可以很方便的在其他地方通过reactTag获取到我们的Component所在的rootView.
然后才是[RCTRootView runApplication], 这里就会调用js里面AppRegistry对应的方法runApplication了, 在AppRegistry.js中有下面的一段注释, 已经解释得很清楚了
8、 因为执行js代码的时候, js端会计算好每个view的布局, 属性等信息, 然后通过调用native的系统方法来完成, 页面的渲染, 页面的布局. 这个过程中就涉及到了js 和native的互调通信了. 这个过程分为两个部分.